import { Direction } from "../Direction";
import Stack from "../Stack";
import Tank from "./Tank";
import AutoActions from "../AutoActions";

export enum TankAction {
    goUp = 'goUp',
    goRight = 'goRight',
    goDown = 'goDown',
    goLeft = 'goLeft',
    shot = 'shot',
}
export class Keyboard2Action {

    private static actions: TankAction[] = [
        TankAction.goUp,
        TankAction.goRight,
        TankAction.goDown,
        TankAction.goLeft,
        TankAction.shot,
    ];
    private static directions: Direction[] = [
        Direction.up,
        Direction.right,
        Direction.down,
        Direction.left,
    ];

    private keys: cc.macro.KEY[];

    constructor(
        upKey: cc.macro.KEY,
        rightKey: cc.macro.KEY,
        downKey: cc.macro.KEY,
        leftKey: cc.macro.KEY,
        shotKey: cc.macro.KEY
    ) {
        this.keys = [
            upKey,
            rightKey,
            downKey,
            leftKey,
            shotKey,
        ];
    }

    public getAction(key: cc.macro.KEY): TankAction | null {
        return Keyboard2Action.actions[this.keys.indexOf(key)];
    }
    public getDirection(key: cc.macro.KEY): Direction | null {
        return Keyboard2Action.directions[this.keys.indexOf(key)];
    }
    public destroy() {
        this.keys = null;
    }
}


export default class TankControl {
    private enteredDirectKey: Stack<Direction>;

    private key2Action: Keyboard2Action;

    private isStart: boolean;

    private tank: Tank;
    private initialPos: cc.Vec2;

    private posX: number;
    private posY: number;
    private direction: Direction;

    private autoMove: AutoActions;
    // private rigidBody: cc.RigidBody;
    // private static zeroSpeed: cc.Vec2 = new cc.Vec2(0, 0);

    private instanceIsDestroyed: boolean = false;

    private tankIsShotting: boolean = false;

    private readonly maxX: number;
    private readonly maxY: number;

    public isAuto: boolean = false;

    private constructor(
        tank: Tank,
        key2Action: Keyboard2Action,
        initialDirection: Direction,
        initialPos?: cc.Vec2
    ) {

        this.maxX = ~~(0.5 * (cc.winSize.width - tank.node.width));
        this.maxY = ~~(0.5 * (cc.winSize.height - tank.node.height));

        this.tank = tank;

        // 按下的方向键队列
        this.enteredDirectKey = new Stack<Direction>({
            action: this.enteredDirectKeyChanged,
            thisArg: this,
        });

        // 键盘按键映射到方向
        this.key2Action = key2Action;

        // 是否已启用
        this.isStart = false;

        this.autoMove = new AutoActions(this.tank, this.stepOne, this);
        // this.rigidBody = this.tank.node.getComponent(cc.RigidBody);
        // this.rigidBody.linearVelocity = TankControl.zeroSpeed;
        // this.rigidBody.linearDamping = 0;
        // this.rigidBody.gravityScale = 0;

        this.setDirection(initialDirection);

        if (!!initialPos) {
            this.initialPos = new cc.Vec2(initialPos.x, initialPos.y);
        } else {
            this.initialPos = new cc.Vec2(
                tank.node.position.x,
                tank.node.position.y
            );
        }
        this.setPos(this.initialPos);
    }

    public get isShotting(): boolean {
        return this.tankIsShotting;
    }

    private onKeyDown(event: cc.Event.EventKeyboard) {
        if (!this.isStart) {
            this.enteredDirectKey.clear();
            return;
        }

        const action = this.key2Action.getAction(event.keyCode);
        switch (action) {
            case TankAction.goUp:
            case TankAction.goRight:
            case TankAction.goDown:
            case TankAction.goLeft:
                const direction = this.key2Action.getDirection(event.keyCode) as Direction;
                if (!this.enteredDirectKey.hasItem(direction)) {
                    this.enteredDirectKey.push(direction);
                }
                break;
            case TankAction.shot:
                this.tankIsShotting = true;
                break;

            default:
                return;
        }
    }
    private onKeyUp(event: cc.Event.EventKeyboard) {
        if (!this.isStart) {
            this.enteredDirectKey.clear();
            return;
        }

        const action = this.key2Action.getAction(event.keyCode);
        switch (action) {
            case TankAction.goUp:
            case TankAction.goRight:
            case TankAction.goDown:
            case TankAction.goLeft:
                const direction = this.key2Action.getDirection(event.keyCode) as Direction;
                if (this.enteredDirectKey.hasItem(direction)) {
                    this.enteredDirectKey.remove(direction);
                }
                break;

            case TankAction.shot:
                this.tankIsShotting = false;

            default:
                return;
        }
    }
    private enteredDirectKeyChanged() {
        const direction = this.enteredDirectKey.top() as Direction;
        switch (direction) {
            case Direction.up:
            case Direction.right:
            case Direction.down:
            case Direction.left:
                if (direction !== this.direction) {
                    this.setDirection(direction);
                    // this.setSpeed();
                } else {
                    // const speed = this.rigidBody.linearVelocity;
                    // if (0 === speed.x && 0 === speed.y) {
                    //     this.setSpeed();
                    // }
                }
                break;

            default:
                // this.rigidBody.linearVelocity = TankControl.zeroSpeed;
                // this.unwatchIsOverBorder();
                return;
        }
    }
    // private isOverBorder(): boolean {
    //     const pos = this.tank.node.position;
    //     if (Math.abs(pos.x) >= this.maxX || Math.abs(pos.y) >= this.maxY) {
    //         // 越界了
    //         this.rigidBody.linearVelocity = TankControl.zeroSpeed;
    //         return true;
    //     }
    // }
    // private setSpeed() {
    //     if (this.isOverBorder()) {
    //         // 越界了
    //         return;
    //     }

    //     const speedBase = this.tank.speed;
    //     const speed = new cc.Vec2(0, 0);
    //     switch (this.direction) {
    //         case Direction.up:
    //             speed.y = speedBase;
    //             break;
    //         case Direction.right:
    //             speed.x = speedBase;
    //             break;
    //         case Direction.down:
    //             speed.y = -speedBase;
    //             break;
    //         case Direction.left:
    //             speed.x = -speedBase;
    //             break;
    //         default:
    //             return;
    //     }
    //     this.rigidBody.linearVelocity = speed;
    //     this.watchIsOverBorder();
    // }
    // private watchIsOverBorder(){
    //     // this.tank.scheduleOnce(this.isOverBorder);
    // }
    // private unwatchIsOverBorder(){
    //     this.tank.unschedule(this.isOverBorder);
    // }
    private getSpeed(): number {
        const speed = this.tank.speed;
        switch (this.direction) {
            case Direction.up:
            case Direction.right:
                return speed;
            case Direction.down:
            case Direction.left:
                return -speed;
            default:
                return 0;
        }
    }
    private stepOne() {
        if (!this.isStart || (!this.isAuto && 0 === this.enteredDirectKey.length())) {
            return;
        }

        let x: number, y: number;
        switch (this.direction) {
            case Direction.up:
            case Direction.down:
                x = this.posX;
                y = this.posY + this.getSpeed();
                break;

            case Direction.left:
            case Direction.right:
                x = this.posX + this.getSpeed();
                y = this.posY;
                break;

            default:
                return;
        }
        this.setPos(x, y);
        return;
    }

    public getPosX() {
        return this.posX;
    }
    public getPosY() {
        return this.posY;
    }
    public getDirection() {
        return this.direction;
    }
    public isMoving() {
        return this.isAuto || 0 < this.enteredDirectKey.length();
    }

    public setDirection(direction: Direction) {
        this.direction = direction;
        // this.rigidBody.linearVelocity = TankControl.zeroSpeed;
        // this.unwatchIsOverBorder();
    }
    public setPos(posOrX: cc.Vec2 | number, y?: number): TankControl {
        let x: number;
        if ('number' !== (typeof posOrX)) {
            x = (posOrX as cc.Vec2).x;
            y = (posOrX as cc.Vec2).y;
        } else {
            x = posOrX as number;
            y = y || 0;
        }

        const isOverX = Math.abs(x) > this.maxX;
        if (isOverX) {
            x = 0 < x ? this.maxX : (-this.maxX);
        }

        const isOverY = Math.abs(y) > this.maxY;
        if (isOverY) {
            y = 0 < y ? this.maxY : (-this.maxY);
        }

        this.posX = x;
        this.posY = y;

        return this;
    }
    public modifyInitialPos(newPos: cc.Vec2): TankControl {
        this.initialPos = new cc.Vec2(newPos.x, newPos.y);
        return this;
    }
    public start(): TankControl {
        if (!this.isStart) {
            this.isStart = true;
            TankControl.controllerStarted();
            this.autoMove.start();
        }
        return this;
    }
    public stop(): TankControl {
        if (this.isStart) {
            this.isStart = false;
            TankControl.controllerStopped();
            this.autoMove.stop();
            // this.rigidBody.linearVelocity = TankControl.zeroSpeed;
            // this.unwatchIsOverBorder();
        }
        return this;
    }
    public resetPos(): TankControl {
        this.setPos(this.initialPos);
        return this;
    }
    public destroy() {
        if (this.instanceIsDestroyed) {
            return;
        }

        // 顺序不能乱
        this.stop();
        TankControl.controllerDestroyed(this);
        this.enteredDirectKey.destroy();
        this.enteredDirectKey = null;
        this.key2Action.destroy();
        this.key2Action = null;
        this.initialPos = null;
        this.tank = null;
        this.instanceIsDestroyed = true;
        this.autoMove.destroy();
        // this.rigidBody = null;
    }
    public isDestroyed(): boolean {
        return this.instanceIsDestroyed;
    }


    private static controllers: TankControl[] = [];
    private static isListening: boolean = false;
    private static controllerStarted() {
        if (!this.isListening && 0 < this.controllers.filter(c => c.isStart).length) {
            cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
            cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
            this.isListening = true;
        }
    }
    private static controllerStopped() {
        if (this.isListening && 0 === this.controllers.filter(c => c.isStart).length) {
            cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
            cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
            this.isListening = false;
        }
    }
    private static controllerDestroyed(controller: TankControl) {
        const index = this.controllers.indexOf(controller);
        if (-1 < index) {
            this.controllers.splice(index, 1);
        }
    }
    private static onKeyDown(event: cc.Event.EventKeyboard) {
        this.controllers.forEach(c => c.isStart && c.onKeyDown(event));
    }
    private static onKeyUp(event: cc.Event.EventKeyboard) {
        this.controllers.forEach(c => c.isStart && c.onKeyUp(event));
    }
    public static regiter(
        tank: Tank,
        directionMapKey: Keyboard2Action,
        initialDirection: Direction,
        autoStart: boolean = true,
        initialPos?: cc.Vec2
    ): TankControl {
        const controller = new TankControl(
            tank,
            directionMapKey,
            initialDirection,
            initialPos
        );
        this.controllers.push(controller);
        if (autoStart) {
            controller.start();
        }
        return controller;
    }
    public static resetAllPos() {
        this.controllers.forEach(c => c.resetPos());
    }
    public static destroyAll() {
        const all = this.controllers.slice();
        this.controllers = [];
        all.forEach(c => c.destroy());
    }
}
